import json
import traceback

from colorama import *
import pymem


debugLevel = 1


def Test(func):
  def Do(*args, **kwargs):
    try:
      return func(*args, **kwargs)
    except Exception as e:
      if debugLevel > 0:
        print(Fore.RED + f'[错误]Test.{func.__name__}.e=', e, Style.RESET_ALL)
        if debugLevel > 1:
          traceback.print_exc()
  return Do


def CSV(path):
  with open(path, encoding='utf8') as f:
    lines = f.read()
  data = []
  for line in lines.split('\n'):
    if ',' not in line:
      continue
    data.append([int(a) for a in line.split(',')])
  return data


def Round(t, d=0):
  return round(t[0], d), round(t[1], d), round(t[2], d)


class Mem:
  def __init__(self, gameName):
    self.gameName = gameName
    self._pointers = {}
    try:
      self.pm = pymem.Pymem(self.exeName)
    except pymem.exception.ProcessNotFound:
      print(Fore.RED, f'[错误]未找到名为“{self.exeName}”的进程。', Style.RESET_ALL)
      self.pm = None
    self.allConfig = {}
    self.LoadExtraConfig()

  def LoadExtraConfig(self):
    for configName, configInfo in self.pointers.get('extraConfig', {}).items():
      if configInfo['decode'] in globals():
        data = globals()[configInfo['decode']](f'{self.gameName}/{configInfo["path"]}')
        self.allConfig[configName] = data

  @Test
  def GetPos(self, configName, xKey, yKey):
    """为GTA5特意设计的算法"""
    x = self.GetSingle(xKey)
    y = self.GetSingle(yKey)
    sortedZPos = sorted(self.allConfig[configName], key=lambda p: abs(p[0] - x * 0.1) + abs(p[1] - y * 0.1))
    z = sortedZPos[0][2] * 0.6 + sortedZPos[1][2] * 0.3 + sortedZPos[2][2] * 0.1
    return x, y, z

  def TpPos(self, configName, xKey, yKey, stateJudgeKey, setXKey1, setYKey1, setZKey1, setXKey2, setYKey2, setZKey2):
    """为GTA5特意设计的算法"""
    x, y, z = self.GetPos(configName, xKey, yKey)
    print('x, y, z=', x, y, z)
    print('stateJudgeKey=', stateJudgeKey)
    state = self.GetAddr(stateJudgeKey)
    print('state=', hex(state), state)
    if state == 0x0:
      print('people tp')
      self.SetSingle(setXKey2, x, True)
      self.SetSingle(setYKey2, y, True)
      self.SetSingle(setZKey2, z, True)
    else:
      print('car tp')
      # self.SetSingle(setXKey2, x, True)
      # self.SetSingle(setYKey2, y, True)
      # self.SetSingle(setZKey2, z + 1.0, True)
      self.SetSingle(setXKey1, x, True)
      self.SetSingle(setYKey1, y, True)
      self.SetSingle(setZKey1, z, True)

  def GetAddr(self, pKey, debug=False):
    pointer = self.pm.base_address
    for offset in self.pointers[pKey][:-1]:
      if offset in self.pointers['pointerPool']:
        offset = self.pointers['pointerPool'][offset]
      offset = eval(offset)
      if debug:
        print('pointer=', hex(pointer))
        print('offset=', hex(offset))
      pointer = self.pm.read_ulonglong(pointer + offset)
    pointer += eval(self.pointers[pKey][-1])
    if debug:
      print(Fore.GREEN, f'[suc]GetAddr.{pKey}.pointer=', hex(pointer), Style.RESET_ALL)
    return pointer

  @Test
  def SetDouble(self, pKey, value):
    if pKey not in self.pointers:
      print(Fore.YELLOW + f'[warn]未找到名为“{pKey}”的指针链。' + Style.RESET_ALL)
      return
    self.pm.write_double(self.GetAddr(pKey), float(value))

  def ChangeBool(self, pKey, hasKey=None):
    if hasKey is not None:
      hasValue = self.GetAddr(hasKey)
      if hasValue == 0x0:
        return
    now = self.GetBool(pKey)
    self.SetBool(pKey, not now)

  @Test
  def GetBool(self, pKey, hasKey=None):
    if hasKey is not None:
      hasValue = self.GetAddr(hasKey)
      if hasValue == 0x0:
        return False
    return self.pm.read_bool(self.GetAddr(pKey))

  @Test
  def GetSingle(self, pKey, debug=False):
    return self.pm.read_float(self.GetAddr(pKey, debug))

  def SetInt(self, pKey, value):
    self.pm.write_int(self.GetAddr(pKey), value)

  @Test
  def SetBool(self, pKey, value):
    self.pm.write_bool(self.GetAddr(pKey), value)

  @Test
  def SetSingle(self, pKey, value, debug=False):
    if pKey not in self.pointers:
      print(Fore.YELLOW + f'[warn]未找到名为“{pKey}”的指针链。' + Style.RESET_ALL)
      return
    self.pm.write_float(self.GetAddr(pKey, debug), float(value))

  def SetSingleByKey(self, pKey, gKey):
    value = self.GetSingle(gKey)
    self.SetSingle(pKey, value)

  @property
  def pointers(self):
    if not self._pointers:
      with open(f'{self.gameName}/pointers.json', encoding='utf8') as f:
        self._pointers = json.loads(f.read())
    return self._pointers

  @property
  def exeName(self):
    return self.pointers['exeName']

  def ReloadPointers(self):
    with open(f'{self.gameName}/pointers.json', encoding='utf8') as f:
      self._pointers = json.loads(f.read())
    self.pm = pymem.Pymem(self.exeName)
    print(Fore.BLUE, f'[Info]ReloadPointers', Style.RESET_ALL)
